/*
 * SunSpotHostApplication.java
 *
 * Created on Jul 21, 2009 4:45:20 PM;
 */
package org.sunspotworld;

import com.sun.spot.io.j2me.radiogram.Radiogram;
import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import com.sun.spot.peripheral.NoRouteException;
import com.sun.spot.peripheral.radio.RadioFactory;
import com.sun.spot.util.IEEEAddress;
import com.sun.spot.util.Utils;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.microedition.io.Connector;

/**
 * Sample Sun SPOT host_mac application
 */
public class SunSpotHostApplication {
	
	private static final String DATABASE_HOST = "csc453-team03.csc.ncsu.edu";
	private static final String DATABASE_NAME = "test";
	private static final String DATABASE_USERNAME = "sensorsadmin";
	private static final String DATABASE_PASSWORD = "csc453Fri1";

	private static final String DATA_PORT = "87";
    private static final String CMD_PORT = "89";
    private static final int CHANNEL = 24;

    private RadiogramConnection xmitConn;

    private IEEEAddress myAddress = new IEEEAddress(RadioFactory.getRadioPolicyManager().getIEEEAddress());

    private Hashtable blinkStates = new Hashtable();
    private Hashtable countDowns  = new Hashtable();

	private Connection dbConn;
	private Statement dbStmt;

    public static final long SECONDS = 1000;
    private static final long POLL_INTERVAL = 2*SECONDS;
    private static final long TIMEOUT = 10*SECONDS;
    private static final int COUNT_FROM = (int)(TIMEOUT / POLL_INTERVAL);
    
    private boolean running = true;

	private Thread pollThread = new Thread() {
        public void run() {
            while (running) {
                pollState();
                decrementCounters();
                Utils.sleep(POLL_INTERVAL);
            }
        }
    };

    private void decrementCounters() {
        Enumeration en = countDowns.keys();
        String key;
        while (en.hasMoreElements()) {
            key = (String)en.nextElement();
            decrement(key);
        }
    }

    private synchronized void decrement(String key) {
        Counter count = (Counter)countDowns.get(key);
        if (count == null) {
            return;
        }
        if (count.decrement()) {
            countDowns.put(key, count);
        } else {
            removeNode(key);
        }
    }

    private void removeNode (String node_mac) {
        countDowns.remove(node_mac);
        blinkStates.remove(node_mac);
        updateState(node_mac, new BlinkState(BlinkState.OFFLINE,(byte)0,(byte)0,(byte)0));
    }

	/**
	 * Print out our radio address.
	 */
	public void run() {
		long ourAddr = RadioFactory.getRadioPolicyManager().getIEEEAddress();
		System.out.println("Our radio address = " + IEEEAddress.toDottedHex(ourAddr));
        RadioFactory.getRadioPolicyManager().setChannelNumber(CHANNEL);
		initDatabase();
        new StopButton(this).setVisible(true);

        RadiogramConnection conn;
		Radiogram dg;
		try {
			conn = (RadiogramConnection) Connector.open("radiogram://:"+DATA_PORT);
            xmitConn = (RadiogramConnection) Connector.open("radiogram://:"+CMD_PORT);
			dg = (Radiogram)conn.newDatagram(conn.getMaximumLength());
		} catch (IOException ex) {
			Logger.getLogger(SunSpotHostApplication.class.getName()).log(Level.SEVERE, null, ex);
			return;
		}
        pollThread.start();
		try {
			while (running) { // no code yet implemented to turn off running
				conn.receive(dg);
				System.out.println("Received packet");

				String node_mac = dg.getAddress();
				long time = dg.readLong();
				int light = dg.readInt();
                byte state = dg.readByte();
                byte red = dg.readByte();
                byte green = dg.readByte();
                byte blue = dg.readByte();

				insert(node_mac, time, light, state, red, green, blue);
			}
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
        if (conn != null) {
            try {
                conn.close();
                conn = null;
            } catch (IOException ex) {
                Logger.getLogger(SunSpotHostApplication.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        close();
	}

    private void close() {
        try {
            running = false;
            pollThread.join();
        } catch (InterruptedException ex) {
            Logger.getLogger(SunSpotHostApplication.class.getName()).log(Level.SEVERE, null, ex);
        }
        if (xmitConn != null) {
            try {
                xmitConn.close();
                xmitConn = null;
            } catch (IOException ex) {
                Logger.getLogger(SunSpotHostApplication.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

	private void initDatabase() {
		try {
			// This finds the driver to connect to the MySQL database
			Class.forName("com.mysql.jdbc.Driver").newInstance();

			// Open a connection to the server, use database "database", with username "user" and password "pass"
			dbConn = DriverManager.getConnection("jdbc:mysql://" + DATABASE_HOST + "/" + DATABASE_NAME +
					"?user=" + DATABASE_USERNAME + "&password=" + DATABASE_PASSWORD);

			dbStmt = dbConn.createStatement();

			dbStmt.execute("CREATE TABLE IF NOT EXISTS tbl_data (`node_mac` char(19) NOT NULL, `inttime` bigint NOT NULL, `light` int(11) NOT NULL, `state` tinyint(4) NOT NULL DEFAULT 0, `red` tinyint(4) NOT NULL DEFAULT 0, `blue` tinyint(4) NOT NULL DEFAULT 0, `green` tinyint(4) NOT NULL DEFAULT 0) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
            dbStmt.execute("CREATE TABLE IF NOT EXISTS `tbl_nodes` (`node_mac` char(19) NOT NULL PRIMARY KEY, `host_mac` char(19) NOT NULL, `state` tinyint(4) NOT NULL DEFAULT 0, `red` tinyint(4) NOT NULL DEFAULT 0, `blue` tinyint(4) NOT NULL DEFAULT 0, `green` tinyint(4) NOT NULL DEFAULT 0) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
            dbStmt.execute("UPDATE `tbl_nodes` SET state = " + BlinkState.OFFLINE + " WHERE host_mac = '" + myAddress + "'");
		} catch (IllegalAccessException ex) {
			ex.printStackTrace();
			System.exit(1);
		} catch (ClassNotFoundException ex) {
			ex.printStackTrace();
			System.exit(1);
		} catch (SQLException ex) {
			ex.printStackTrace();
			System.exit(1);
		} catch (InstantiationException ex) {
			ex.printStackTrace();
			System.exit(1);
		}
	}

	private void insert(String node_mac, long time, int light, byte state, byte red, byte green, byte blue) {
        countDowns.put(node_mac, new Counter(COUNT_FROM));
        if (!blinkStates.containsKey(node_mac)) {
            addSPOT(node_mac);
        }
		try {
			String tuple = "('" + node_mac + "', " + time + "," + light + "," + ((int)state &0xFF) + "," + ((int)red &0xFF) + "," + ((int)green &0xFF) + "," + ((int)blue &0xFF) + ")";
			System.out.println("(node_mac, inttime, light, state, red, green, blue) = " + tuple);
			dbStmt.execute("INSERT INTO tbl_data (node_mac, inttime, light, state, red, green, blue) VALUES " + tuple);
		} catch (SQLException ex) {
			Logger.getLogger(SunSpotHostApplication.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

    private void addSPOT(String node_mac) {
        updateState(node_mac, new BlinkState(BlinkState.ONLINE_NOTBLINK, (byte)0,(byte)0,(byte)0));
    }

    private void sendBlink(String node_mac, BlinkState state) {
        try {
            Radiogram xrg = (Radiogram) xmitConn.newDatagram(xmitConn.getMaximumLength());
            xrg.setAddress(node_mac);
            xrg.reset();
            xrg.writeByte(state.getState());
            xrg.writeByte(state.getRed());
            xrg.writeByte(state.getGreen());
            xrg.writeByte(state.getBlue());
            xmitConn.send(xrg);
            state.setState(BlinkState.ONLINE_NOTBLINK);
            updateState(node_mac, state);
        } catch (NoRouteException nrex) {
            decrement(node_mac);
        } catch (IOException ex) {
            Logger.getLogger(SunSpotHostApplication.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void shutdown() {
        Enumeration en = blinkStates.keys();
        while (en.hasMoreElements()) {
            removeNode((String)en.nextElement());
        }
        System.exit(0);
    }

    private void updateState(String node_mac, BlinkState state) {
        try {
            System.out.println(node_mac + ": " + state.getRed() + ", " + state.getGreen() + ", " + state.getBlue());
            blinkStates.put(node_mac, state);
            dbStmt.execute("REPLACE INTO tbl_nodes (node_mac, host_mac, state, red, green, blue) VALUES ('" + node_mac + "','" + myAddress + "'," + ((int)state.getState() & 0xFF) + ", "  + ((int)state.getRed() & 0xFF) + ", " + ((int)state.getGreen() & 0xFF) + ", " + ((int)state.getBlue() & 0xFF) + ")");
        } catch (SQLException ex) {
            Logger.getLogger(SunSpotHostApplication.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void pollState() {
        try {
            String query = "SELECT * FROM tbl_nodes WHERE 1=0 ";
            Enumeration en = blinkStates.keys();
            while (en.hasMoreElements()) {
                query += "OR node_mac = '" + en.nextElement() + "'";
            }
            System.out.println(query);
            Statement stmt = dbConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
            ResultSet rs = stmt.executeQuery(query);
            while (rs.next()) {
                BlinkState state = new BlinkState((byte)rs.getInt("state"), (byte)rs.getInt("red"), (byte)rs.getInt("green"), (byte)rs.getInt("blue"));
                sendBlink(rs.getString("node_mac"), state);
                System.out.println(rs.getString("node_mac") + ": " + state.getRed() + ", " + state.getGreen() + ", " + state.getBlue());
            }
            stmt.close();
        } catch (SQLException ex) {
            Logger.getLogger(SunSpotHostApplication.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static void main(String[] args) {
		SunSpotHostApplication app = new SunSpotHostApplication();
		app.run();
	}
}
